﻿using System;
using System.Collections.Generic;
using System.Threading;

namespace Picnic.Threading
{
    /// <summary>
    /// Represents a distributed reader/writer lock.
    /// </summary>
    public class DistributedReaderWriterLock<TLockKey> : DisposableObject
    {
        private readonly object syncLock = new object();
        private readonly Dictionary<TLockKey, ReaderWriterLockSlim> locks
            = new Dictionary<TLockKey, ReaderWriterLockSlim>();

        /// <summary>
        /// Initializes a new instance of DistributedReaderWriterLock class.
        /// </summary>
        public DistributedReaderWriterLock()
        {
        }

        /// <summary>
        /// Initializes a new instance of DistributedReaderWriterLock class with specified lock key comparer.
        /// </summary>
        public DistributedReaderWriterLock(EqualityComparer<TLockKey> comparer)
        {
            locks = new Dictionary<TLockKey, ReaderWriterLockSlim>(comparer);
        }

        /// <summary>
        /// Tries to enter the lock in read mode.
        /// </summary>
        public void EnterReadLock(TLockKey lockKey)
        {
            GetReaderWriterLock(lockKey).EnterReadLock();
        }

        /// <summary>
        /// Tries to enter the lock in write mode.
        /// </summary>
        public void EnterWriteLock(TLockKey lockKey)
        {
            GetReaderWriterLock(lockKey).EnterWriteLock();
        }

        /// <summary>
        /// Reduces the recursion count for read mode, and exits read mode if the resulting count is 0 (zero).
        /// </summary>
        public void ExitReadLock(TLockKey lockKey)
        {
            GetReaderWriterLock(lockKey).ExitReadLock();
        }

        /// <summary>
        /// Reduces the recursion count for write mode, and exits write mode if the resulting count is 0 (zero).
        /// </summary>
        public void ExitWriteLock(TLockKey lockKey)
        {
            GetReaderWriterLock(lockKey).ExitWriteLock();
        }

        /// <summary>
        /// Tries to read value in read-lock mode by specified lock key.
        /// </summary>
        public TResult LockRead<TResult>(TLockKey lockKey, Func<TResult> reader)
        {
            var readerWriterLock = GetReaderWriterLock(lockKey);

            readerWriterLock.EnterReadLock();
            try
            {
                return reader();
            }
            finally
            {
                readerWriterLock.ExitReadLock();
            }
        }

        /// <summary>
        /// Tries to write value in write-lock mode by specified lock key.
        /// </summary>
        public void LockWrite(TLockKey lockKey, Action writer)
        {
            var readerWriterLock = GetReaderWriterLock(lockKey);

            readerWriterLock.EnterWriteLock();
            try
            {
                writer();
            }
            finally
            {
                readerWriterLock.ExitWriteLock();
            }
        }

        private ReaderWriterLockSlim GetReaderWriterLock(TLockKey lockKey)
        {
            ReaderWriterLockSlim readerWriterLock;
            lock (syncLock)
            {
                if (!locks.TryGetValue(lockKey, out readerWriterLock))
                {
                    readerWriterLock = new ReaderWriterLockSlim();
                    locks.Add(lockKey, readerWriterLock);
                }
            }
            return readerWriterLock;
        }

    }
}
